/* The ns package provides the net-score.org functionality. */
package ns

import (
	"appengine"
	"appengine/datastore"
	"appengine/user"
	"fmt"
	"net/http"
	"strings"
	"time"
)

const (
	ListLimit = 1000
)

func init() {
	http.HandleFunc("/user/detail/", resultsDetail)
	http.HandleFunc("/ns/detail/", resultsDetail)
	http.HandleFunc("/detail/", resultsDetail)
	http.HandleFunc("/ns/detailJSON/", resultsDetailJSON)
	http.HandleFunc("/ns/results/", resultsAdmin)
	http.HandleFunc("/user/results/", resultsUser)
	http.HandleFunc("/user/developerResults/", resultsDev)
}

type Detail struct {
	Raw    *RawData
	Body   map[string]interface{}
	Header map[string]string
}

/*The resultsDev handler displays a list of results for a developer user.

The handler allows developer to access the test results that are linked to an
active DevKey.

Access control for this page is controlled by the app.yaml file.
*/
func resultsDev(w http.ResponseWriter, r *http.Request) {
	gt := time.Now().Unix()
	c := appengine.NewContext(r)
	path := strings.Split(r.URL.Path, "/")
	var keyString string
	if len(path) == 3 && path[2] != "" {
		keyString = path[2]
	} else if len(path) > 3 && path[3] != "" {
		keyString = path[3]
	} else {
		c.Errorf("resultsDev bad key = \"%s\"", r.URL.Path)
		http.NotFound(w, r)
		return
	}

	//Limit developer results to an active DevKey.
	u := user.Current(c)
	var err error
	if u == nil {
		var loginUrl string
		loginUrl, err = user.LoginURL(c, r.URL.Path)
		if err != nil {
			c.Errorf("resultsDev:user.LoginURL err = %v", err)
			http.Error(w, err.Error(),
				http.StatusInternalServerError)
		}
		http.Redirect(w, r, loginUrl, 307)
		return
	}
	if CheckSiteKey(u, c, keyString) == false {
		c.Errorf("resultsDev:CheckSiteKey err = %v", keyString)
		http.Error(w, err.Error(), http.StatusForbidden)
		return
	}

	//Get some data.
	q := datastore.NewQuery("TestRecord").Filter("DevKey =",
		keyString).Order("-GetTime").Limit(ListLimit)
	var trs []TestRecord
	_, err = q.GetAll(c, &trs)
	if err != nil {
		c.Errorf("resultsDev:q.GetAll err = %v", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	//Convert the list of structs to a list of JSON strings.
	var JSONList []string
	for i := range trs {
		if trs[i].TestDataKey == nil {
			continue
		}
		var tmp []byte
		tmp, err = trs[i].MarshalListUserJSON()
		if err != nil {
			c.Errorf("resultsDev:q.Count err = %v", err)
			http.Error(w, err.Error(),
				http.StatusInternalServerError)
		} else {
			JSONList = append(JSONList, string(tmp))
		}
	}

	//Join the list into a JavaScript list.  Only use the full slice.
	JSONdata := []byte(fmt.Sprintf("[%s]",
		strings.Join(JSONList[:len(JSONList)], ", ")))

	//Set the data.
	var fc map[string]string
	fc, err = newConfig(resultListConfig, c, r, gt)
	if err != nil {
		c.Errorf("resultsDev:newConfig err = %v", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	fc["resultlistSetData"] = fmt.Sprintf("%s", JSONdata)

	h := HTMLHeader{
		Title: "net-score",
		ScriptList: []string{"https://www.google.com/jsapi",
			VersionFile("/", "strings-en-comp.js", c),
			VersionFile("/", "result-list-comp.js", c)},
		StyleList: []string{VersionFile("/", "flock-comp.css", c)},
		JsConfig:  fc,
	}
	p := &HTMLPage{
		Head:   h,
		OnLoad: "resultlistStart();",
	}

	//Return the page.
	err = staticSet.ExecuteTemplate(w, "page", p)
	if err != nil {
		c.Errorf("resultsDev:staticSet.ExecuteTemplate err = %v", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

/*The resultsUser handler displays a list of results for a user.

The handler allows the user to access the test results that are linked to an
specific userID.

Access control for this page is controlled by the app.yaml file.
*/
func resultsUser(w http.ResponseWriter, r *http.Request) {
	gt := time.Now().Unix()
	c := appengine.NewContext(r)

	//get the user if we have one.
	u := user.Current(c)
	if u == nil {
		c.Errorf("resultsUser: unknown user")
		http.Error(w, "resultsUser: unknown user",
			http.StatusInternalServerError)
		return
	}
	// Get some data.
	q := datastore.NewQuery("TestRecord").Filter("UserID =",
		u.ID).Order("-GetTime").Limit(ListLimit)
	var err error
	var trs []TestRecord
	_, err = q.GetAll(c, &trs)
	if err != nil {
		c.Errorf("resultsUser:q.GetAll err = %v", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	//Convert the list of structs to a list of JSON strings.
	var JSONList []string
	for i := range trs {
		if trs[i].TestDataKey == nil {
			continue
		}
		var tmp []byte
		tmp, err = trs[i].MarshalListUserJSON()
		if err != nil {
			c.Errorf("resultsUser:q.Count err = %v", err)
			http.Error(w, err.Error(),
				http.StatusInternalServerError)
		} else {
			JSONList = append(JSONList, string(tmp))
		}
	}

	//Join the list into a JavaScript list.  Only use the full slice.
	JSONdata := []byte(fmt.Sprintf("[%s]",
		strings.Join(JSONList[:len(JSONList)], ", ")))

	//Set the data.
	var fc map[string]string
	fc, err = newConfig(resultListConfig, c, r, gt)
	if err != nil {
		c.Errorf("resultsUser:newConfig err = %v", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	fc["resultlistSetData"] = fmt.Sprintf("%s", JSONdata)

	h := HTMLHeader{
		Title: "net-score",
		ScriptList: []string{"https://www.google.com/jsapi",
			VersionFile("/", "strings-en-comp.js", c),
			VersionFile("/", "result-list-comp.js", c)},
		StyleList: []string{VersionFile("/", "flock-comp.css", c)},
		JsConfig:  fc,
	}
	p := &HTMLPage{
		Head:   h,
		OnLoad: "resultlistStart();",
	}

	//return the page!
	err = staticSet.ExecuteTemplate(w, "page", p)
	if err != nil {
		c.Errorf("resultsUser:staticSet.ExecuteTemplate err = %v", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

/*The resultsAdmin handler displays a list of results for an administrator.

The handler allows the administrator to access all test results.

Access control for this page is controlled by the app.yaml file.
*/
func resultsAdmin(w http.ResponseWriter, r *http.Request) {
	gt := time.Now().Unix()
	c := appengine.NewContext(r)

	//Get some data.
	q := datastore.NewQuery("TestRecord").Order("-GetTime").Limit(ListLimit)
	var err error
	var trs []TestRecord
	_, err = q.GetAll(c, &trs)
	if err != nil {
		c.Errorf("resultsAdmin:q.GetAll err = %v", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	//Convert the list of structs to a list of JSON strings.
	JSONList := make([]string, len(trs))
	for i := range trs {
		var tmp []byte
		tmp, err = trs[i].MarshalListJSON()
		if err != nil {
			c.Errorf("resultsAdmin:q.Count err = %v", err)
			http.Error(w, err.Error(),
				http.StatusInternalServerError)
		} else {
			JSONList[i] = string(tmp)
		}
	}

	//Join the list into a JavaScript list.
	JSONdata := []byte(fmt.Sprintf("[%s]", strings.Join(JSONList, ", ")))

	//Set the data.
	var fc map[string]string
	fc, err = newConfig(resultListConfig, c, r, gt)
	if err != nil {
		c.Errorf("resultsAdmin:newConfig err = %v", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	fc["resultlistSetData"] = fmt.Sprintf("%s", JSONdata)

	h := HTMLHeader{
		Title: "net-score",
		ScriptList: []string{"https://www.google.com/jsapi",
			VersionFile("/", "strings-en-comp.js", c),
			VersionFile("/", "result-list-comp.js", c)},
		StyleList: []string{VersionFile("/", "flock-comp.css", c)},
		JsConfig:  fc,
	}
	p := &HTMLPage{
		Head:   h,
		OnLoad: "resultlistStart();",
	}

	//Return the page.
	err = staticSet.ExecuteTemplate(w, "page", p)
	if err != nil {
		c.Errorf("resultsAdmin:staticSet.ExecuteTemplate err = %v", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

/*The resultsDetail handler returns the details of a result.

The handler allows the user to access all the details of a test result.

The result can be accessed if any of the following is true:
 * The user is an administrator
 * The DevKey of the result matches a user's active DevKey
 * The UserID matches the current user's UserID

Access control for this page is controlled by the app.yaml file.
*/
func resultsDetail(w http.ResponseWriter, r *http.Request) {
	gt := time.Now().Unix()
	c := appengine.NewContext(r)
	path := strings.Split(r.URL.Path, "/")
	var keyString string
	if len(path) == 3 && path[2] != "" {
		keyString = path[2]
	} else if len(path) > 3 && path[3] != "" {
		keyString = path[3]
	} else {
		c.Errorf("resultsDetail bad key = \"%s\"", r.URL.Path)
		http.NotFound(w, r)
		return
	}
	key := datastore.NewKey(c, "TestRecord", keyString, 0, nil)
	tr := new(TestRecord)
	var err error
	var JSONdata []byte
	var fc map[string]string
	err = datastore.Get(c, key, tr)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	u := user.Current(c)

	//Limit developer results to an active DevKey
	if tr.UserID != "" && user.IsAdmin(c) == false {
		if u == nil {
			var loginUrl string
			loginUrl, err = user.LoginURL(c, r.URL.Path)
			if err != nil {
				c.Errorf("resultsDetail:user.LoginURL = %v",
					err)
				http.Error(w, err.Error(),
					http.StatusInternalServerError)
			}
			http.Redirect(w, r, loginUrl, 307)
			return
		}
		if CheckSiteKey(u, c, tr.DevKey) == false && tr.UserID != u.ID {
			c.Errorf("resultsDetail wrong DevKey = \"%s\"",
				keyString)
			http.Error(w, "resultsDetail wrong DevKey",
				http.StatusForbidden)
			return
		}
	}

	//TODO(gavaletz) #22 Add the ability to "lock" a result

	JSONdata, err = tr.MarshalJSON(c)
	if err != nil {
		c.Errorf("resultsDetail:tr.MarshalJSON = %v", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	fc, err = newConfig(resultDetailConfig, c, r, gt)
	if err != nil {
		c.Errorf("resultsDetail:newConfig = %v", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	fc["resultdetailSetData"] = fmt.Sprintf("%s", JSONdata)
	fc["datastoreSetResultKey"] = fmt.Sprintf("'%s'", keyString)

	h := HTMLHeader{
		Title: "net-score",
		ScriptList: []string{"https://www.google.com/jsapi",
			VersionFile("/", "strings-en-comp.js", c),
			VersionFile("/", "result-detail-comp.js", c)},
		StyleList: []string{VersionFile("/", "flock-comp.css", c)},
		JsConfig:  fc,
	}
	p := &HTMLPage{
		Head:   h,
		OnLoad: "resultdetailStart();",
	}

	//Return the page.
	err = staticSet.ExecuteTemplate(w, "page", p)
	if err != nil {
		c.Errorf("resultsDetail:staticSet.ExecuteTemplate err = %v",
			err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

/*The resultsDetailJSON handler returns the JSON encoded details of a result.

The handler allows the administrator to access all the details of a test result
encoded as a JSON string.

Access control for this page is controlled by the app.yaml file.
*/
func resultsDetailJSON(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	path := strings.Split(r.URL.Path, "/")
	var keyString string
	if len(path) == 3 && path[2] != "" {
		keyString = path[2]
	} else if len(path) > 3 && path[3] != "" {
		keyString = path[3]
	} else {
		c.Errorf("resultsDetailJSON bad key = \"%s\"", r.URL.Path)
		http.NotFound(w, r)
		return
	}

	key := datastore.NewKey(c, "TestRecord", keyString, 0, nil)
	tr := new(TestRecord)
	var err error
	err = datastore.Get(c, key, tr)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	var JSONdata []byte
	JSONdata, err = tr.MarshalJSON(c)
	if err != nil {
		c.Errorf("DETAIL json.Marshal err = %v", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	//Return the details.
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	fmt.Fprintf(w, "%s", JSONdata)
}
